﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LuaInterface;
using IronPython.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting;
using IronRuby.Hosting;
using IronRuby.Runtime;
using IronRuby;
using Refl.Extensions;
using Microsoft.Scripting.Hosting;
using System.Diagnostics;
using System.IO;
using Jint;

namespace Refl.Dreaming
{
    public class Script : Codex , IDream
    {
        //---------------------------------------------------------------------
        // This class was designed using a delegate approach combined with
        // an object of type dynamic for the engine. The delegate approach
        // is very usefull, because it prevents the code from checking
        // the language every time the methods Invoke, InsertObject and
        // RetrieveObject are called.
        //---------------------------------------------------------------------
        #region 'Properties'
        internal dynamic _Engine;
        internal ScriptScope _Scope = null;
        internal ScriptInfo Info;
        public string Name
        {
            get { if (Info._RequireNameUpdate) { this.UpdateName(); } return Info._Name; }
        }
        internal List<DreamError> _Errors;
        public List<DreamError> Errors
        {
            get { return _Errors; }
        }
        internal bool _HasErrors = false;
        public bool HasErrors
        {
            get { return _HasErrors; }
        }
        //bool-returning action
        internal Func<bool> _Invoker;
        internal Action<string, object> _Setter;
        internal Func<string, dynamic> _Getter;
        #endregion

        public Script(string code, ScriptLanguage language) : base(code, (Language)language)
        {
            Info = new ScriptInfo();
            Info.Language = language;
            UpdateName();

            switch (language)
            {
                case ScriptLanguage.Brainfuck:
                    break;
                case ScriptLanguage.IronPython:
                    _Engine = Python.CreateEngine();
                    _Scope = _Engine.CreateScope();
                    _Invoker = ipy_invoke;
                    _Getter = ipy_get;
                    _Setter = ipy_set;
                    break;
                case ScriptLanguage.IronRuby:
                    _Engine = Ruby.CreateEngine();
                    _Scope = _Engine.CreateScope();
                    _Invoker = iru_invoke;
                    _Getter = iru_get;
                    _Setter = iru_set;
                    break;
                case ScriptLanguage.JavaScript:
                    _Engine = new JintEngine();
                    _Engine.DisableSecurity();
                    _Scope = null;
                    _Invoker = jint_invoke;
                    _Getter = jint_get;
                    _Setter = jint_set;
                    break;
                case ScriptLanguage.Lua:
                    _Engine = new Lua();
                    _Scope = null; //The scope is integrated in the engine
                    _Invoker = lua_invoke;
                    _Getter = lua_get;
                    _Setter = lua_set;
                    break;
            }
        }

        internal void UpdateName()
        {
            //the dream name is a MD5 hash of its characteristics.
            //TODO: Implement Json and generate a object for this.
            string assembled = (string.Format("[{0}][{1}][{2}]", _Str_Code, Info.Language.ToString(), Info.Threaded.ToString()));
            Info._Name = assembled.GetMD5Hash();
        }

        public bool Invoke()
        {
            return _Invoker();
        }

        public void InsertObject(string name, object obj)
        {
            _Setter(name, obj);
        }

        public dynamic RetrieveObject(string name)
        {
            return _Getter(name);
        }
        public T RetrieveObject<T>(string name)
        {
            object o = _Getter(name);
            if (o.GetType() != typeof(T))
            {
                return default(T);
            }
            else
            {
                return (T)o;
            }
        }

        //----------------------------------------------------------------------
        //Private delegate methods
        #region IronPython
        internal void ipy_set(string name, object obj)
        {
            _Scope.SetVariable(name, obj);
        }
        internal dynamic ipy_get(string name)
        {
            return _Scope.GetVariable(name);
        }
        internal bool ipy_invoke()
        {
            try
            {
                ScriptSource source = _Engine.CreateScriptSourceFromString(_Str_Code, SourceCodeKind.AutoDetect);
                source.Execute(_Scope);
            }
            catch (Exception ex)
            {
                #if DEBUG
                    Console.WriteLine();
                #endif
                if (_Errors == null) { _Errors = new List<DreamError>(); }
                _Errors.Add(new DreamError("Script:Invoke.IronPython/Exception", ex.Message, null, null, true));
                _HasErrors = true;
                return false;
            }
            return true;
        }
        #endregion
        #region Lua
        internal void lua_set(string name, object obj)
        {
            _Engine[name] = obj;
        }
        internal dynamic lua_get(string name)
        {
            return _Engine[name];
        }
        internal bool lua_invoke()
        {
            try
            {
                _Engine.DoString(_Str_Code);
            }
            catch (LuaException lex)
            {
                if (_Errors == null) { _Errors = new List<DreamError>(); }
                _Errors.Add(new DreamError("Script:Invoke.Lua/LuaException", lex.Message, null, null, true));
                _HasErrors = true;
                return false;
            }
            catch (Exception ex)
            {
                #if DEBUG
                    Console.WriteLine();
                #endif
                if (_Errors == null) { _Errors = new List<DreamError>(); }
                _Errors.Add(new DreamError("Script:Invoke.Lua/Exception", ex.Message, null, null, true));
                _HasErrors = true;
                return false;
            }
            return true;
        }
        #endregion
        #region IronRuby
        internal void iru_set(string name, object obj)
        {
            _Scope.SetVariable(name, obj);
        }
        internal dynamic iru_get(string name)
        {
            return _Scope.GetVariable(name);
        }
        internal bool iru_invoke()
        {
            try
            {
                List<string> sp = new List<string>(); sp.Add("..\\..\\..\\Dreams\\IronRuby\\Lib\\ironruby\\");
                _Engine.SetSearchPaths(sp);
                ScriptSource source = _Engine.CreateScriptSourceFromString(_Str_Code, SourceCodeKind.AutoDetect);
                source.Execute(_Scope);
            }
            catch (Exception ex)
            {
                #if DEBUG
                    Console.WriteLine();
                #endif
                if (_Errors == null) { _Errors = new List<DreamError>(); }
                _Errors.Add(new DreamError("Script:Invoke.IronRuby/Exception", ex.Message, null, null, true));
                _HasErrors = true;
                return false;
            }
            return true;
        }
        #endregion
        #region JavaScript
        internal void jint_set(string name, object obj)
        {
            _Engine.SetParameter(name, obj);
        }
        internal dynamic jint_get(string name)
        {
            return _Engine.Run("return " + name + ";");
        }
        internal bool jint_invoke()
        {
            try
            {
                _Engine.Run(_Str_Code);
            }
            catch (Exception ex)
            {
                #if DEBUG
                    Console.WriteLine();
                #endif
                if (_Errors == null) { _Errors = new List<DreamError>(); }
                _Errors.Add(new DreamError("Script:Invoke.JavaScript/Exception", ex.Message, null, null, true));
                _HasErrors = true;
                return false;
            }
            return true;
        }
        #endregion
    }
}
